#!/usr/bin/env python3 
# -*- coding:utf-8 -*-

class Student(object):
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return 'Student object name :%s' % self.name


print(Student("123"))

s = Student("abc")
print(s)


# __iter__
# 如果一个类想被用于for ... in循环，类似list或tuple那样，就必须实现一个__iter__()方法，
# 该方法返回一个迭代对象，然后，Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值，直到遇到StopIteration错误时退出循环

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1  # 初始化两个计数

    def __iter__(self):
        return self

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        if self.a > 100:  # 设置退出条件
            raise StopIteration()
        return self.a  # 返回下一个值


for n in Fib():
    print(n)


# Fib实例虽然能作用于for循环，看起来和list有点像，但是，把它当成list来使用还是不行，比如，取第5个元素
# print(Fib()[5])
# 提示 TypeError: 'Fib' object does not support indexing
# 要表现得像list那样按照下标取出元素，需要实现__getitem__()方法

class Fib(object):
    def __getitem__(self, item):
        a, b = 1, 1
        for x in range(item):
            a, b = b, a + b
        return a


f = Fib()
print(f[0])  # 1
print(f[2])

# list 切片
print(list(range(100))[5:10])


# 对于Fib却报错。原因是__getitem__()传入的参数可能是一个int，也可能是一个切片对象slice
# 修改 fib class

class Fib(object):
    def __getitem__(self, item):
        if isinstance(item, int):  # item是索引
            a, b = 1, 1
            for x in range(item):
                a, b = b, a + b
            return a
        if isinstance(item, slice):  # item是切片
            start = item.start
            stop = item.stop
            if start is None:
                start = 0
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L


f = Fib()
print(f[0])
print(f[0:5])
print(f[:10:2])

print(list(range(100))[:10:2])


# __getattr__
# 正常情况下，当我们调用类的方法或属性时，如果不存在，就会报错
class Student(object):

    def __init__(self):
        self.name = 'Michael'


print(Student().name)


# 调用name属性，没问题，但是，调用不存在的score属性，就有问题了
# print(Student().score) AttributeError: 'Student' object has no attribute 'score'
# 要避免这个错误，除了可以加上一个score属性外，Python还有另一个机制，那就是写一个__getattr__()方法，动态返回一个属性

class Student(object):
    def __init__(self):
        self.name = "123"

    def __getattr__(self, item):
        if item == 'score':
            return 99

        if item == 'age':  # 返回函数也是 ok 的,只是最后调用的地方需要修改
            return lambda: 25

        raise AttributeError('\'Student\' object has no attribute \'%s\'' % item)


s = Student()
print(s.score)  # 99

print(s.age())


# 注意，只有在没有找到属性的情况下，才调用__getattr__，已有的属性，比如name，不会在__getattr__中查找


# 链式调用：


class Chain(object):
    def __init__(self, path=''):
        self._path = path

    def __getattr__(self, path):
        return Chain('%s/%s' % (self._path, path))

    def __str__(self):
        return self._path

    def __call__(self, param):
        print('cal __call__(%s)' % param)
        return Chain('%s/%s' % (self._path, param))


print(Chain().user.name.list)

"""
__call__
一个对象实例可以有自己的属性和方法，当我们调用实例方法时，我们用instance.method()来调用。能不能直接在实例本身上调用呢？在Python中，答案是肯定的。

任何类，只需要定义一个__call__()方法，就可以直接对实例进行调用
"""

print(Chain().users('michael').repos)

"""
# /status/user/timeline/list
# Chain().status.user.timeline.list调用分析
# 首先执行Chain()返回一个实例对象C1(path = '')，
# 通过实例对象C1来获取status属性，因为C1中不存在status属性，所以就会调用
# __getattr__()来尝试获取status属性，接着通过__getattr__()方法返回
# 带参数status的实例对象C2(path = '/status')，然后通过实例对象C2来获取user属性，
# C2中不存在user属性，接着调用__getattr__()方法返回带参数user
# 的实例对象C3(path = '/status/user')，然后通过实例对象C3来获取timeline属性，
# 因C3不存在timeline属性，故调用__getattr__()方法返回带参数timeline
# 的实例对象C4(path = '/status/user/timeline')，通过实例对象C4来获取list属性，
# 又因C4中不存在list属性，调用__getattr__()方法返回带参数list
# 的实例对象C5(path = '/status/user/timeline/list')，
# 最后通过调用__str__()方法来打印实例对象C5，即返回/status/user/timeline/list
# 具体参考见下面的测试结果
print(Chain().status.user.timeline.list)
print('--------------------------------------')

# GET /users/:user/repos
# :user替换为实际用户名
# /users/Lollipop/repos
# Chain().users('Lollipop').repos 调用分析
# 首先执行Chain()返回一个实例对象Q1(path = '')，
# 通过实例对象Q1来获取users属性，因为Q1中不存在users属性，
# 所以就会调用__getattr__()方法尝试获取users属性，接着通过
# __getattr__()方法返回带参数users的实例对象Q2(path = '/users')，
# 然后因为通过()直接调用实例对象Q2，并带参数'Lollipop'，故会调用
# __call__()方法，返回了带参数Lollipop的实例对象Q3(path = '/users/Lollipop')，
# 接着通过实例对象Q3来获取repos属性，又因Q3中不存在repos属性，即会调用
# __getattr__()方法返回带参数repos的实例对象Q4(path = '/users/Lollipop/repos')
# 最后通过调用__str__()方法来打印实例对象Q4，即返回/users/Lollipop/repos
# 具体参考见下面的测试结果
print(Chain().users('Lollipop').repos)
"""
